Fork me on GitHub

Python 多任务-线程、进程与协程

前言:

  1. 多线程;
  2. 多进程;
  3. 进程、线程对比;
  4. 多协程;
  5. 进程、线程、协程对比。

Python 多任务-线程、进程与协程

什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。

一、线程

线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

进程和线程.png

并行/并发

02-CPU及线程介绍.png

并发

指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)

并行

指的是任务数小于等于cpu核数,即任务真的是一起执行的

多线程图解

03-线程.png

共享全局变量与问题

在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据。

问题

如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确。(即线程非安全)

同步/异步

同步:单线程(从上到下依次执行顺序)

异步:多线程(开启多个任务一同执行,互不影响)

互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

锁的好处

  • 确保了某段关键代码只能由一个线程从头到尾完整地执行

锁的坏处

  • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
    由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁(一种 bug)

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

避免死锁

  • 程序设计时要尽量避免(银行家算法)
  • 添加超时时间等

案例

1.多线程体验

# Python 解释器中有一个模块专门控制线程,实现多任务

import threading
import time


def send_msg(num):
for i in range(num):
print("send msg", i + 1)
time.sleep(0.1)


def receive_msg(num):
for i in range(num):
print("receive msg", i + 1)
time.sleep(0.2)


if __name__ == '__main__':
# 创建线程对象(target=函数名, name=线程别名, args=参数, kwargs=字典参数)
t1 = threading.Thread(target=send_msg, name="send", args=(3,))
t2 = threading.Thread(target=receive_msg, name="receive", kwargs={"num": 3})

# 三个 API
print(threading.enumerate())
print(threading.current_thread())
print(threading.active_count())

# 守护线程
t1.setDaemon(True)
t2.setDaemon(True)

# 开启线程
t1.start()
t2.start()

# 线程等待
t1.join()
t2.join()

print("*** main thread ***")

2.自定义线程类

import threading
import time


class SendThread(threading.Thread):
def __init__(self, num):
# threading.Thread.__init__(self)
super(SendThread, self).__init__()
# super().__init__()

self.num = num

def send_msg(self):
for i in range(5):
print("send msg", i + 1)
time.sleep(0.1)

def run(self):
self.send_msg()


class ReceiveThread(threading.Thread):
def receive_msg(self):
for i in range(5):
print("receive msg", i + 1)
time.sleep(0.2)

def run(self):
self.receive_msg()


if __name__ == '__main__':
t1 = SendThread(3)
t2 = ReceiveThread()

t1.start()
t2.start()

print("*** main thread ***")

二、进程

运行的程序以及运行时用到的资源这个整体称之为进程,是系统进行资源分配和调度的一个独立单位。

01-进程.png

进程间通信-Queue

进程间通信:运行的程序之间的数据共享。

三、进程、线程对比

功能

  • 进程,能够完成多任务,比如 在一台电脑上能够同时运行多个 QQ。
  • 线程,能够完成多任务,比如 一个 QQ 中的多个聊天窗口。

定义的不同

  • 进程是系统进行资源分配和调度的一个独立单位。
  • 线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
  • 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

区别

  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率,
  • 线线程不能够独立执行,必须依存在进程中。
  • 可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人。

优缺点

  • 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。

四、协程

协程,又称微线程,纤程。英文名Coroutine。

02-协程.png

03-迭代器和生成器.png

greenlet

为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单

安装方式

使用如下命令安装 greenlet 模块:

pip3 list: 验证已安装的python第三方插件(如果没有pip3会提示安装)

sudo pip3 install greenlet

gevent

greenlet已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent

其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

安装方式

pip3 install gevent

协程和线程差异

在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU 上下文这么简单。

操作系统为了程序运行的高效性每个线程都有自己缓存 Cache 等等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。

但是协程的切换只是单纯的操作 CPU 的上下文,所以一秒钟切换个上百万次系统都抗的住。

五、进程、线程、协程对比

  1. 进程是资源分配的单位
  2. 线程是操作系统调度的单位
  3. 进程切换需要的资源很最大,效率很低
  4. 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
  5. 协程切换任务资源很小,效率高
  6. 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发
-------------本文结束感谢您的阅读-------------

本文标题:Python 多任务-线程、进程与协程

文章作者:曹永林

发布时间:2018年07月10日 - 00:07

最后更新:2018年07月28日 - 10:07

原始链接:http://jovelin.cn/2018/07/10/Python 多任务-线程、进程与协程/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。